#ifndef _NTH_LIB_H
#include "nth-lib.h"
#endif

#ifndef _STDINT_H
#include <stdint.h>
#endif

#ifndef _STDIO_H
#include <stdio.h>
#endif

#ifndef _STDLIB_H
#include <stdlib.h>
#endif

#ifndef _STRING_H
#include <string.h>
#endif

int Add_Nth_Numbers(struct Nth_Addition * nth_addition){

	int error = 0;
	size_t new_length = 0;

	if (!nth_addition
	 || !nth_addition->operand_one.number_length
	 || !nth_addition->operand_one.number_buffer
	 || !nth_addition->operand_two.number_length
	 || !nth_addition->operand_two.number_buffer
	 || !nth_addition->sum.number_length
	 || !nth_addition->sum.number_buffer){
		return 1;
	}

	if (nth_addition->operand_one.number_length
	  < nth_addition->operand_two.number_length){
		return 1;
	}

	if (nth_addition->operand_one.number_length % sizeof(mp_ptr)){

		new_length = 0;
		new_length += nth_addition->operand_one.number_length / sizeof(mp_ptr);
		new_length += 1;
		new_length *= sizeof(mp_ptr);

		error = Set_Nth_Number_Length(
			&nth_addition->operand_one,
			new_length
		);

		if (error){
			return 1;
		}
	}

	if (nth_addition->operand_two.number_length % sizeof(mp_ptr)){

		new_length = 0;
		new_length += nth_addition->operand_two.number_length / sizeof(mp_ptr);
		new_length += 1;
		new_length *= sizeof(mp_ptr);

		error = Set_Nth_Number_Length(
			&nth_addition->operand_two,
			new_length
		);

		if (error){
			return 1;
		}
	}

	new_length = 0;
	new_length += nth_addition->operand_one.number_length;
	new_length += nth_addition->operand_two.number_length;

	error = Set_Nth_Number_Length(
		&nth_addition->sum,
		new_length
	);

	if (error){
		return 1;
	}

	mpn_add(
		nth_addition->sum.number_buffer,
		nth_addition->operand_one.number_buffer,
		nth_addition->operand_one.number_length / sizeof(mp_ptr),
		nth_addition->operand_two.number_buffer,
		nth_addition->operand_two.number_length / sizeof(mp_ptr)
	);

	return 0;
}

int Append_Nth_Path(
	char ** nth_path,
	char * append_path
){
	char * new_path = 0;
	size_t new_length = 0;
	size_t old_length = 0;

	if (!nth_path){
		return 1;
	}

	if (!append_path){
		return 1;
	}

	old_length = 0;

	if (*nth_path){
		old_length += strlen(*nth_path);
	}

	new_length = 0;
	new_length += old_length;
	new_length += 1; // '/'
	new_length += strlen(append_path);
	new_length += 1; // null terminator

	new_path = realloc(
		(*nth_path),
		new_length
	);

	if (!new_path){
		return 1;
	}

	new_path[old_length] = '/';

	strcpy(
		&new_path[old_length + 1],
		append_path
	);

	(*nth_path) = new_path;

	return 0;
}

int Clean_Nth_Number(struct Nth_Number * nth_number){

	if (!nth_number){
		return 1;
	}

	if (nth_number->number_buffer){
		free(nth_number->number_buffer);
	}

	nth_number->number_buffer = 0;
	nth_number->number_length = 0;

	return 0;
}

int Create_Nth_Path(
	char ** absolute_path
){
	return 0;
}

int Create_Nth_Program(
	struct Nth_Program ** nth_program
){
	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	(*nth_program) = calloc(
		1,
		sizeof(struct Nth_Program)
	);

	if (!(*nth_program)){
		return NTH_ERROR_NO_MEMORY;
	}

	return 0;
}

int Destroy_Nth_Program(
	struct Nth_Program ** nth_program
){
	size_t fail_index = 0;
	size_t fail_count = 0;
	char ** fail_functions = 0;
	char ** fail_messages = 0;

	size_t var_index = 0;
	size_t var_count = 0;
	struct Nth_Variable * var_array = 0;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	if (!(*nth_program)){
		return NTH_ERROR_NULL_POINTER;
	}

	fail_index = 0;
	fail_count = (*nth_program)->failure_count;
	fail_functions = (*nth_program)->failure_functions;
	fail_messages = (*nth_program)->failure_messages;

	while (fail_index < fail_count){

		if ((fail_functions)
		 && (fail_functions[fail_index])){
			free(fail_functions[fail_index]);
		}

		if ((fail_messages)
		 && (fail_messages[fail_index])){
			free(fail_messages[fail_index]);
		}

		fail_index++;
	}

	if (fail_functions){
		free(fail_functions);
	}

	if (fail_messages){
		free(fail_messages);
	}

	var_index = 0;
	var_count = 0;
	var_count += (*nth_program)->variable_count;

	var_array = (*nth_program)->variable_array;

	if (var_array){

		while (var_index < var_count){

			if (var_array[var_index].variable_name){
				free(var_array[var_index].variable_name);
			}

			if (var_array[var_index].variable_value){
				free(var_array[var_index].variable_value);
			}

			var_index++;
		}

		free(var_array);
	}

	free(*nth_program);
	(*nth_program) = 0;

	return 0;
}

int Divide_Nth_Numbers(struct Nth_Division * nth_division){

	int error = 0;
	size_t new_length = 0;

	if (!nth_division
	 || !nth_division->operand_one.number_length
	 || !nth_division->operand_one.number_buffer
	 || !nth_division->operand_two.number_length
	 || !nth_division->operand_two.number_buffer){
		return 1;
	}

	if (nth_division->operand_one.number_length
	  < nth_division->operand_two.number_length){
		return 1;
	}

	if (nth_division->operand_one.number_length % sizeof(mp_ptr)){

		new_length = 0;
		new_length += nth_division->operand_one.number_length / sizeof(mp_ptr);
		new_length += 1;
		new_length *= sizeof(mp_ptr);

		error = Set_Nth_Number_Length(
			&nth_division->operand_one,
			new_length
		);

		if (error){
			return 1;
		}
	}

	if (nth_division->operand_two.number_length % sizeof(mp_ptr)){

		new_length = 0;
		new_length += nth_division->operand_two.number_length / sizeof(mp_ptr);
		new_length += 1;
		new_length *= sizeof(mp_ptr);

		error = Set_Nth_Number_Length(
			&nth_division->operand_two,
			new_length
		);

		if (error){
			return 1;
		}
	}

	if (nth_division->operand_one.number_length
	 >= nth_division->operand_two.number_length){

		new_length = 0;
		new_length += nth_division->operand_one.number_length;
		new_length -= nth_division->operand_one.number_length;
		new_length += sizeof(mp_ptr);

	} else {

		new_length = 0;
		new_length += sizeof(mp_ptr);
	}

	error = Set_Nth_Number_Length(
		&nth_division->quotient,
		new_length
	);

	if (error){
		return 1;
	}

	new_length = 0;
	new_length += nth_division->operand_two.number_length;

	error = Set_Nth_Number_Length(
		&nth_division->remainder,
		new_length
	);

	if (error){
		return 1;
	}

	mpn_tdiv_qr(
		nth_division->quotient.number_buffer,
		nth_division->remainder.number_buffer,
		0,
		nth_division->operand_one.number_buffer,
		nth_division->operand_one.number_length / sizeof(mp_ptr),
		nth_division->operand_two.number_buffer,
		nth_division->operand_two.number_length / sizeof(mp_ptr)
	);

	return 0;
}

int Evaluate_Nth_Expression(
	struct Nth_Program * nth_program,
	struct Nth_Number * nth_number
){
	int error = 0;

	size_t plugin_index = 0;
	size_t plugin_count = 0;
	struct Nth_Plugin * plugin_array = 0;

	size_t name_diff = 0;

	if (!nth_program){
		return NTH_ERROR_UNSPECIFIED;
	}

	if (nth_program->arg_index >= nth_program->argc){

		Push_Nth_Failure(
			nth_program,
			"Evaluate_Nth_Expression",
			"No operation specified"
		);

		return 1;
	}

	plugin_index = 0;
	plugin_count = nth_program->plugin_count;
	plugin_array = nth_program->plugin_array;

	while (plugin_index < plugin_count){

		if (!plugin_array[plugin_index].plugin_name){
			plugin_index++;
			continue;
		}

		name_diff = strcmp(
			plugin_array[plugin_index].plugin_name,
			nth_program->argv[nth_program->arg_index]
		);

		if (name_diff){
			plugin_index++;
			continue;
		}

		nth_program->arg_index++;

		if (!plugin_array[plugin_index].plugin_callback){

			Push_Nth_Failure(
				nth_program,
				"Evaluate_Nth_Expression",
				"Expression callback is null"
			);

			return NTH_ERROR_NULL_POINTER;
		}

		error = plugin_array[plugin_index].plugin_callback(
			nth_program,
			nth_number
		);

		if (error){

			Push_Nth_Failure(
				nth_program,
				"Evaluate_Nth_Expression",
				"Expression callback failed"
			);

			return NTH_ERROR_UNSPECIFIED;
		}

		break;
	}

	if (plugin_index >= plugin_count){

		Push_Nth_Failure(
			nth_program,
			"Evaluate_Nth_Expression",
			"Keyword not recognized"
		);

		return 1;
	}

	return 0;
}

int Export_Nth_Results(
	struct Nth_Program * nth_program,
	struct Nth_Number * number_array,
	size_t number_count
){
	int error = 0;

	char * number_path = 0;
	size_t number_index = 0;

	struct Nth_Variable results_dir;

	char index_str[24];

	struct Nth_Number nth_index;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	if (!number_count){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Number count is zero"
		);

		return NTH_ERROR_ZERO_LENGTH;
	}

	if (!number_array){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Number array is null"
		);

		return NTH_ERROR_NULL_POINTER;
	}

	results_dir.variable_name = "results-dir";
	results_dir.variable_value = 0;

	error = Get_Nth_Variable(
		nth_program,
		&results_dir
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Failed to get results dir path"
		);

		return 1;
	}

	error = Export_Nth_Directory(
		results_dir.variable_value
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Failed to export results directory"
		);

		return 1;
	}

	error = Create_Nth_Path(&number_path);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Could not create export path"
		);

		return 1;
	}

	error = Set_Nth_Path_Prefix(
		&number_path,
		results_dir.variable_value
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Could not set export path prefix"
		);

		Destroy_Nth_Path(&number_path);
		return 1;
	}

	nth_index.number_buffer = &nth_program->stack_index;
	nth_index.number_length = sizeof(nth_program->stack_index);

	error = Write_Hex_Nth_Number(
		&nth_index,
		index_str,
		sizeof(index_str)
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Could not write result index path"
		);

		Destroy_Nth_Path(&number_path);
		return 1;
	}

	error = Append_Nth_Path(
		&number_path,
		index_str
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Failed to append result index path"
		);

		Destroy_Nth_Path(&number_path);
		return 1;
	}

	error = Export_Nth_Directory(
		number_path
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Export_Nth_Results",
			"Failed to export result directory"
		);

		Destroy_Nth_Path(&number_path);
		return 1;
	}

	nth_index.number_buffer = &number_index;
	nth_index.number_length = sizeof(number_index);

	while (number_index < number_count){

		error = Write_Hex_Nth_Number(
			&nth_index,
			index_str,
			sizeof(index_str)
		);

		if (error){

			Push_Nth_Failure(
				nth_program,
				"Export_Nth_Results",
				"Failed to write number index path"
			);

			Destroy_Nth_Path(&number_path);
			return 1;
		}

		error = Append_Nth_Path(
			&number_path,
			index_str
		);

		if (error){

			Push_Nth_Failure(
				nth_program,
				"Export_Nth_Results",
				"Failed to set number index path"
			);

			Destroy_Nth_Path(&number_path);
			return 1;
		}

		error = Write_Nth_File(
			number_path,
			number_array[number_index].number_buffer,
			number_array[number_index].number_length
		);

		if (error){

			Push_Nth_Failure(
				nth_program,
				"Export_Nth_Results",
				"Failed to write number file"
			);

			Destroy_Nth_Path(&number_path);
			return 1;
		}

		number_index++;
	}

	if (number_path){
		Destroy_Nth_Path(&number_path);
	}

	return 0;
}

int Get_Next_Nth_Argument(
	struct Nth_Program * nth_program,
	char ** next_argument
){
	int arg_index = 0;
	int argc = 0;
	char ** argv = 0;;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	if (!next_argument){
		return NTH_ERROR_NULL_POINTER;
	}

	arg_index = nth_program->arg_index;
	argc = nth_program->argc;
	argv = nth_program->argv;

	if (arg_index >= argc){

		Push_Nth_Failure(
			nth_program,
			"Get_Next_Nth_Argument",
			"No more arguments left"
		);

		return 1;
	}

	(*next_argument) = argv[arg_index];

	return 0;
}

int Get_Nth_File_Size(
	char * file_name,
	size_t * file_size
){
	int error = 0;
	long int _file_size = 0;
	FILE * file = 0;

	if (!file_name){
		return NTH_ERROR_NULL_POINTER;
	}

	if (!file_size){
		return NTH_ERROR_NULL_POINTER;
	}

	file = fopen(file_name, "r");

	if (!file){
		return NTH_ERROR_UNSPECIFIED;
	}

	error = fseek(
		file,
		0,
		SEEK_END
	);

	if (error){
		fclose(file);
		return NTH_ERROR_UNSPECIFIED;
	}

	_file_size = ftell(file);

	if (_file_size < 0){
		fclose(file);
		return NTH_ERROR_UNSPECIFIED;
	}

	(*file_size) = _file_size;

	fclose(file);
	return 0;
}

int Get_Nth_Variable(
	struct Nth_Program * nth_program,
	struct Nth_Variable * nth_variable
){
	size_t name_diff = 0;

	size_t var_index = 0;
	size_t var_count = 0;
	struct Nth_Variable * var_array = 0;

	if (!nth_program
	 || !nth_variable
	 || !nth_variable->variable_name){
		return 1;
	}

	var_index = 0;
	var_count = 0;
	var_count += nth_program->variable_count;

	var_array = nth_program->variable_array;

	while (var_index < var_count){

		name_diff = strcmp(
			var_array[var_index].variable_name,
			nth_variable->variable_name
		);

		if (!name_diff){
			break;
		}

		var_index++;
	}

	if (var_index >= var_count){
		return 1;
	}

	nth_variable->variable_value = var_array[var_index].variable_value;

	return 0;
}

int Destroy_Nth_Path(
	char ** absolute_path
){
	if (!absolute_path){
		return 1;
	}

	if (!(*absolute_path)){
		return 1;
	}

	free(*absolute_path);
	(*absolute_path) = 0;

	return 0;
}

int Iterate_Nth_Argument(
	struct Nth_Program * nth_program
){
	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	if (nth_program->arg_index >= nth_program->argc){

		Push_Nth_Failure(
			nth_program,
			"Iterate_Nth_Argument",
			"Cannot iterate argument out of range"
		);

		return NTH_ERROR_UNSPECIFIED;
	}

	nth_program->arg_index++;

	return 0;
}

int Multiply_Nth_Numbers(struct Nth_Multiplication * nth_multiplication){

	int error = 0;
	size_t req_dest_length = 0;

	size_t op_one_length = 0;
	size_t op_two_length = 0;

	if (!nth_multiplication
	 || !nth_multiplication->operand_one.number_buffer
	 || !nth_multiplication->operand_one.number_length
	 || !nth_multiplication->operand_two.number_buffer
	 || !nth_multiplication->operand_two.number_length
	 || !nth_multiplication->product.number_buffer
	 || !nth_multiplication->product.number_length){
		return 1;
	}

	if (nth_multiplication->operand_one.number_length % sizeof(mp_ptr)){

		op_one_length = 0;
		op_one_length += nth_multiplication->operand_one.number_length / sizeof(mp_ptr);
		op_one_length += 1;
		op_one_length *= sizeof(mp_ptr);

		error = Set_Nth_Number_Length(
			&nth_multiplication->operand_one,
			op_one_length
		);

		if (error){
			return 1;
		}
	}

	if (nth_multiplication->operand_two.number_length % sizeof(mp_ptr)){

		op_two_length = 0;
		op_two_length += nth_multiplication->operand_two.number_length / sizeof(mp_ptr);
		op_two_length += 1;
		op_two_length *= sizeof(mp_ptr);

		error = Set_Nth_Number_Length(
			&nth_multiplication->operand_two,
			op_two_length
		);

		if (error){
			return 1;
		}
	}

	req_dest_length = 0;
	req_dest_length += nth_multiplication->operand_one.number_length;
	req_dest_length += nth_multiplication->operand_two.number_length;

	error = Set_Nth_Number_Length(
		&nth_multiplication->product,
		req_dest_length
	);

	if (error){
		return 1;
	}

	mpn_mul(
		nth_multiplication->product.number_buffer,
		nth_multiplication->operand_one.number_buffer,
		nth_multiplication->operand_one.number_length / sizeof(mp_ptr),
		nth_multiplication->operand_two.number_buffer,
		nth_multiplication->operand_two.number_length / sizeof(mp_ptr)
	);

	return 0;
}

int Parse_Bin_Nth_Number(
	char * string_buffer,
	size_t string_length,
	struct Nth_Number * nth_number
){

	return 0;
}

int Parse_Dec_Nth_Number(
	char * string_buffer,
	size_t string_length,
	struct Nth_Number * nth_number
){

	return 0;
}

int Parse_Hex_Nth_Number(
	char * string_buffer,
	size_t string_length,
	struct Nth_Number * nth_number
){
	char * string_ptr = 0;
	char * string_end = 0;

	uint8_t * number_ptr = 0;
	uint8_t * number_end = 0;

	size_t required_length = 0;
	size_t actual_length = 0;

	uint8_t hex_table[256];

	if (!nth_number
	 || !nth_number->number_length
	 || !nth_number->number_buffer
	 || !string_length
	 || !string_buffer){
		return 1;
	}

	required_length = 0;
	required_length += string_length;
	required_length /= 2;
	required_length += 1; // string_length might be odd

	actual_length = 0;
	actual_length += nth_number->number_length;

	if (actual_length < required_length){
		return 1;
	}

	number_ptr = nth_number->number_buffer;
	number_end = nth_number->number_buffer;
	number_end += nth_number->number_length;
	number_end -= 1;

	string_end = string_buffer;
	string_ptr = string_buffer;
	string_ptr += string_length;
	string_ptr -= 1;

	memset(hex_table, 0, sizeof(hex_table));

	hex_table[ '0' ] = 0x00;
	hex_table[ '1' ] = 0x01;
	hex_table[ '2' ] = 0x02;
	hex_table[ '3' ] = 0x03;
	hex_table[ '4' ] = 0x04;
	hex_table[ '5' ] = 0x05;
	hex_table[ '6' ] = 0x06;
	hex_table[ '7' ] = 0x07;
	hex_table[ '8' ] = 0x08;
	hex_table[ '9' ] = 0x09;

	hex_table [ 'A' ] = 0x0A;
	hex_table [ 'B' ] = 0x0B;
	hex_table [ 'C' ] = 0x0C;
	hex_table [ 'D' ] = 0x0D;
	hex_table [ 'E' ] = 0x0E;
	hex_table [ 'F' ] = 0x0F;

	hex_table [ 'a' ] = 0x0A;
	hex_table [ 'b' ] = 0x0B;
	hex_table [ 'c' ] = 0x0C;
	hex_table [ 'd' ] = 0x0D;
	hex_table [ 'e' ] = 0x0E;
	hex_table [ 'f' ] = 0x0F;

	while (string_ptr >= string_end){

		size_t hex_index = 0;

		(*number_ptr) = 0;

		hex_index = 0;
		hex_index += (*string_ptr);

		(*number_ptr) += hex_table[hex_index] * 0x01;

		string_ptr--;

		hex_index = 0;
		hex_index += (*string_ptr);

		(*number_ptr) += hex_table[hex_index] * 0x10;

		string_ptr--;
		number_ptr++;
	}

	return 0;
}

int Parse_Oct_Nth_Number(
	char * string_buffer,
	size_t string_length,
	struct Nth_Number * nth_number
){

	return 0;
}

int Print_Nth_Failures(
	struct Nth_Program * nth_program
){
	char empty_func[] = "(unspecified)";
	char empty_msg[]  = "(unspecified)";

	size_t failure_count = 0;
	char ** failure_functions = 0;
	char ** failure_messages = 0;
	size_t fail_index = 0;

	size_t longest_func_name = 0;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	failure_count     = nth_program->failure_count;
	failure_functions = nth_program->failure_functions;
	failure_messages  = nth_program->failure_messages;

	fail_index = 0;

	while (fail_index < failure_count){

		size_t name_length = 0;

		if ((failure_functions != 0)
		 && (failure_functions[fail_index] != 0)){

			name_length = strlen(failure_functions[fail_index]);

		} else {

			name_length = sizeof(empty_func) - 1;
		}

		if (name_length > longest_func_name)
			longest_func_name = name_length;

		fail_index++;
	}

	fail_index = 0;

	while (fail_index < failure_count){

		int name_length = 0;
		int pad_count = 0;

		int space_index = 0;

		char * name = 0;
		char * msg = 0;

		if ((failure_functions != 0)
		 && (failure_functions[fail_index] != 0)){

			name = failure_functions[fail_index];
			name_length = strlen(failure_functions[fail_index]);

		} else {

			name = empty_func;
			name_length = sizeof(empty_func) - 1;
		}

		pad_count = longest_func_name - name_length;

		if ((failure_messages != 0)
		 && (failure_messages[fail_index] != 0)){

			msg = failure_messages[fail_index];

		} else {

			msg = empty_msg;
		}

		fprintf(stderr, "%s:", name);

		while (space_index <= pad_count){
			fputc(' ', stderr);
			space_index++;
		}

		fprintf(
			stderr,
			"%s\n",
			msg
		);

		fail_index++;
	}

	return 0;
}

int Push_Nth_Failure(
	struct Nth_Program * nth_program,
	char * function_name,
	char * error_message
){
	size_t length = 0;
	size_t index = 0;

	char ** new_func_ptr = 0;
	char ** new_msg_ptr = 0;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	new_func_ptr = realloc(
		nth_program->failure_functions,
		(nth_program->failure_count + 1) * sizeof(char *)
	);

	if (!new_func_ptr){
		return 1;
	} else {
		nth_program->failure_functions = new_func_ptr;
	}

	new_msg_ptr = realloc(
		nth_program->failure_messages,
		(nth_program->failure_count + 1) * sizeof(char *)
	);

	if (!new_msg_ptr){

		new_func_ptr = realloc(
			nth_program->failure_functions,
			nth_program->failure_count * sizeof(char *)
		);

		if (new_func_ptr){
			nth_program->failure_functions = new_func_ptr;
		}

		return 1;

	} else {
		nth_program->failure_messages = new_msg_ptr;
	}

	nth_program->failure_count += 1;

	index = nth_program->failure_count - 1;

	if (function_name){
		length = strlen(function_name);
		nth_program->failure_functions[index] = malloc(length + 1);
	} else {
		nth_program->failure_functions[index] = 0;
	}

	if (nth_program->failure_functions[index] && function_name){

		memcpy(
			nth_program->failure_functions[index],
			function_name,
			length
		);

		nth_program->failure_functions[index][length] = 0;
	}

	if (error_message){
		length = strlen(error_message);
		nth_program->failure_messages[index] = malloc(length + 1);
	} else {
		nth_program->failure_messages[index] = 0;
	}

	if (nth_program->failure_messages[index] && error_message){

		memcpy(
			nth_program->failure_messages[index],
			error_message,
			length
		);

		nth_program->failure_messages[index][length] = 0;
	}

	return 0;
}

int Push_Nth_Plugin(
	struct Nth_Program * nth_program,
	struct Nth_Plugin * nth_plugin
){
	size_t new_plugin_count = 0;
	struct Nth_Plugin * new_plugin_array = 0;

	size_t old_plugin_count = 0;
	struct Nth_Plugin * old_plugin_array = 0;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	if (!nth_plugin){

		Push_Nth_Failure(
			nth_program,
			"Push_Nth_Plugin",
			"Plugin pointer is null"
		);

		return NTH_ERROR_NULL_POINTER;
	}

	old_plugin_count = nth_program->plugin_count;
	old_plugin_array = nth_program->plugin_array;

	new_plugin_count = old_plugin_count + 1;

	new_plugin_array = realloc(
		old_plugin_array,
		new_plugin_count * sizeof(struct Nth_Plugin)
	);

	if (!new_plugin_array){

		Push_Nth_Failure(
			nth_program,
			"Push_Nth_Plugin",
			"Failed to allocate new plugin"
		);

		return NTH_ERROR_UNSPECIFIED;
	}

	memcpy(
		&new_plugin_array[new_plugin_count - 1],
		nth_plugin,
		sizeof(struct Nth_Plugin)
	);

	nth_program->plugin_array = new_plugin_array;
	nth_program->plugin_count = new_plugin_count;

	return 0;
}

int Read_Nth_File(
	char * file_name,
	void * read_buffer,
	size_t buffer_length
){
	char * buffer_ptr = 0;
	long int total_bytes_read = 0;
	long int bytes_read = 0;
	FILE * file = 0;

	if (!file_name){
		return NTH_ERROR_NULL_POINTER;
	}

	if (!buffer_length){
		return NTH_ERROR_ZERO_LENGTH;
	}

	if (!read_buffer){
		return NTH_ERROR_NULL_POINTER;
	}

	if (((long int) buffer_length) < 0){
		return NTH_ERROR_UNSPECIFIED;
	}

	buffer_ptr = read_buffer;

	file = fopen(file_name, "r");

	if (!file){
		return NTH_ERROR_UNSPECIFIED;
	}

	while (total_bytes_read < ((long int) buffer_length)){

		bytes_read = fread(
			&buffer_ptr[total_bytes_read],
			1,
			buffer_length - total_bytes_read,
			file
		);

		if (bytes_read < 0)
			break;

		total_bytes_read += bytes_read;
	}

	fclose(file);
	return 0;
}

int Read_Nth_Stack_Index(
	struct Nth_Program * nth_program
){
	int error = 0;

	char * stack_index_path = 0;
	struct Nth_Variable results_dir;

	FILE * stack_file = 0;
	size_t bytes_read = 0;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	results_dir.variable_name = "results-dir";
	results_dir.variable_value = 0;

	error = Get_Nth_Variable(
		nth_program,
		&results_dir
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Read_Nth_Stack_Index",
			"Failed to get results-dir variable"
		);

		return 1;
	}

	error = Create_Nth_Path(&stack_index_path);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Read_Nth_Stack_Index",
			"Failed to create stack index path"
		);

		return 1;
	}

	error = Set_Nth_Path_Prefix(
		&stack_index_path,
		results_dir.variable_value
	);

	if (error){

		Destroy_Nth_Path(&stack_index_path);

		Push_Nth_Failure(
			nth_program,
			"Read_Nth_Stack_Index",
			"Failed to set prefix of stack index path"
		);

		return 1;
	}

	error = Set_Nth_Path_Suffix(
		&stack_index_path,
		"stack-index.bin"
	);

	if (error){

		Destroy_Nth_Path(&stack_index_path);

		Push_Nth_Failure(
			nth_program,
			"Read_Nth_Stack_Index",
			"Failed to set suffix of stack index path"
		);

		return 1;
	}

	stack_file = fopen(
		stack_index_path,
		"r"
	);

	Destroy_Nth_Path(&stack_index_path);

	if (!stack_file){
		nth_program->stack_index = 0;
		return 0;
	}

	bytes_read = fread(
		&nth_program->stack_index,
		sizeof(nth_program->stack_index),
		1,
		stack_file
	);

	if (bytes_read != 1){
		fclose(stack_file);
		return NTH_ERROR_UNSPECIFIED;
	}

	fclose(stack_file);
	return 0;
}

int Set_Nth_Path_Prefix(
	char ** absolute_path,
	char * prefix_path
){
	char * new_path_ptr = 0;
	size_t new_path_length = 0;

	if (!absolute_path){
		return 1;
	}

	if (!prefix_path){
		return 1;
	}

	new_path_length = strlen(prefix_path);

	new_path_ptr = realloc(
		(*absolute_path),
		new_path_length + 1
	);

	memcpy(
		new_path_ptr,
		prefix_path,
		new_path_length + 1
	);

	(*absolute_path) = new_path_ptr;

	return 0;
}

int Set_Nth_Path_Suffix(
	char ** absolute_path,
	char * suffix_path
){
	char * old_path_ptr = 0;
	size_t old_path_length = 0;

	char * new_path_ptr = 0;
	size_t new_path_length = 0;

	if (!absolute_path){
		return 1;
	}

	if (!(*absolute_path)){
		return 1;
	}

	if (!suffix_path){
		return 1;
	}

	old_path_ptr = (*absolute_path);
	old_path_length = strlen(old_path_ptr);

	new_path_length = 0;
	new_path_length += old_path_length;
	new_path_length += 1; // '/'
	new_path_length += strlen(suffix_path);
	new_path_length += 1; // null terminator

	new_path_ptr = realloc(
		old_path_ptr,
		new_path_length
	);

	if (!new_path_ptr){
		return 1;
	}

#ifdef _WIN32
	new_path_ptr[old_path_length] = '\\';
#else
	new_path_ptr[old_path_length] = '/';
#endif

	strcpy(
		&new_path_ptr[old_path_length + 1],
		suffix_path
	);

	new_path_ptr[new_path_length - 1] = 0;

	(*absolute_path) = new_path_ptr;

	return 0;
}

int Set_Nth_Variable(
	struct Nth_Program * nth_program,
	struct Nth_Variable * nth_variable
){
	char * variable_name = 0;
	char * variable_value = 0;

	size_t variable_name_length = 0;
	size_t variable_value_length = 0;

	size_t name_diff = 0;
	size_t var_index = 0;

	size_t var_count = 0;
	struct Nth_Variable * var_array = 0;

	size_t old_var_count = 0;
	struct Nth_Variable * old_var_array = 0;

	size_t new_var_index = 0;
	size_t new_var_count = 0;
	size_t new_var_array_size = 0;
	struct Nth_Variable * new_var_array = 0;

	if (!nth_program
	 || !nth_variable
	 || !nth_variable->variable_name
	 || !nth_variable->variable_value){
		return 1;
	}

	var_index = 0;

	var_count = 0;
	var_count += nth_program->variable_count;

	var_array = nth_program->variable_array;

	while (var_index < var_count){

		name_diff = strcmp(
			var_array[var_index].variable_name,
			nth_variable->variable_name
		);

		if (!name_diff){
			break;
		}

		var_index++;
	}

	if (var_index >= var_count){

		old_var_array = nth_program->variable_array;
		old_var_count = 0;
		old_var_count += nth_program->variable_count;

		new_var_count = 0;
		new_var_count += old_var_count;
		new_var_count += 1;

		new_var_array_size = 0;
		new_var_array_size += new_var_count;
		new_var_array_size *= sizeof(struct Nth_Variable);

		new_var_array = realloc(
			old_var_array,
			new_var_array_size
		);

		if (!new_var_array){

			Push_Nth_Failure(
				nth_program,
				"Set_Nth_Variable",
				"Failed to allocate new variable array"
			);

			return 1;
		}

		new_var_index = 0;
		new_var_index += new_var_count;
		new_var_index -= 1;

		new_var_array[new_var_index].variable_name = 0;
		new_var_array[new_var_index].variable_value = 0;

		nth_program->variable_array = new_var_array;
		nth_program->variable_count = new_var_count;

		variable_name_length = 0;
		variable_name_length += strlen(nth_variable->variable_name);
		variable_name_length += 1;

		variable_name = malloc(variable_name_length);

		if (!variable_name){

			Push_Nth_Failure(
				nth_program,
				"Set_Nth_Variable",
				"Failed to allocate new variable name"
			);

			return 1;
		}

		memcpy(
			variable_name,
			nth_variable->variable_name,
			variable_name_length
		);

		new_var_array[new_var_index].variable_name = variable_name;

		var_index = 0;
		var_index += new_var_count;
		var_index -= 1;

		var_array = new_var_array;
		var_count = new_var_count;

	} else if (var_array[var_index].variable_value){

		free(var_array[var_index].variable_value);
		var_array[var_index].variable_value = 0;
	}

	variable_value_length = 0;
	variable_value_length += strlen(nth_variable->variable_value);
	variable_value_length += 1;

	variable_value = malloc(variable_value_length);

	if (!variable_value){

		Push_Nth_Failure(
			nth_program,
			"Set_Nth_Variable",
			"Failed to allocate variable value"
		);

		return 1;
	}

	memcpy(
		variable_value,
		nth_variable->variable_value,
		variable_value_length
	);

	var_array[var_index].variable_value = variable_value;

	return 0;
}

int Subtract_Nth_Numbers(struct Nth_Subtraction * nth_subtraction){

	int error = 0;
	size_t new_length = 0;

	if (!nth_subtraction
	 || !nth_subtraction->operand_one.number_length
	 || !nth_subtraction->operand_one.number_buffer
	 || !nth_subtraction->operand_two.number_length
	 || !nth_subtraction->operand_two.number_buffer
	 || !nth_subtraction->difference.number_length
	 || !nth_subtraction->difference.number_buffer){
		return 1;
	}

	if (nth_subtraction->operand_one.number_length
	  < nth_subtraction->operand_two.number_length){
		return 1;
	}

	if (nth_subtraction->operand_one.number_length % sizeof(mp_ptr)){

		new_length = 0;
		new_length += nth_subtraction->operand_one.number_length / sizeof(mp_ptr);
		new_length += 1;
		new_length *= sizeof(mp_ptr);

		error = Set_Nth_Number_Length(
			&nth_subtraction->operand_one,
			new_length
		);

		if (error){
			return 1;
		}
	}

	if (nth_subtraction->operand_two.number_length % sizeof(mp_ptr)){

		new_length = 0;
		new_length += nth_subtraction->operand_two.number_length / sizeof(mp_ptr);
		new_length += 1;
		new_length *= sizeof(mp_ptr);

		error = Set_Nth_Number_Length(
			&nth_subtraction->operand_two,
			new_length
		);

		if (error){
			return 1;
		}
	}

	if (nth_subtraction->operand_one.number_length
	  > nth_subtraction->operand_two.number_length){

		new_length = 0;
		new_length += nth_subtraction->operand_one.number_length;

	} else {

		new_length = 0;
		new_length += nth_subtraction->operand_two.number_length;
	}

	error = Set_Nth_Number_Length(
		&nth_subtraction->difference,
		new_length
	);

	if (error){
		return 1;
	}

	mpn_sub(
		nth_subtraction->difference.number_buffer,
		nth_subtraction->operand_one.number_buffer,
		nth_subtraction->operand_one.number_length / sizeof(mp_ptr),
		nth_subtraction->operand_two.number_buffer,
		nth_subtraction->operand_two.number_length / sizeof(mp_ptr)
	);

	return 0;
}

int Write_Bin_Nth_Number(
	struct Nth_Number * nth_number,
	char * string_buffer,
	size_t string_length
){
	uint8_t * number_ptr = 0;
	uint8_t * number_end = 0;

	char * string_ptr = 0;

	size_t required_string_length = 0;
	size_t actual_string_length = 0;

	if (!nth_number
	 || !nth_number->number_length
	 || !nth_number->number_buffer
	 || !string_length
	 || !string_buffer){
		return 1;
	}

	actual_string_length = 0;
	actual_string_length += string_length;

	required_string_length = 0;
	required_string_length += nth_number->number_length;
	required_string_length *= 8; // bits per byte
	required_string_length += 1; // null character

	if (actual_string_length
	  < required_string_length){
		return 1;
	}

	number_end = nth_number->number_buffer;
	number_ptr = nth_number->number_buffer;
	number_ptr += nth_number->number_length;
	number_ptr -= 1;

	string_ptr = string_buffer;

	while (((*number_ptr) == 0)
	    && (number_ptr > number_end)){
		number_ptr--;
	}

	while (number_ptr >= number_end){

		if ((*number_ptr) & 0x80){
			string_ptr[0] = '1';
		} else {
			string_ptr[0] = '0';
		}

		if ((*number_ptr) & 0x40){
			string_ptr[1] = '1';
		} else {
			string_ptr[1] = '0';
		}

		if ((*number_ptr) & 0x20){
			string_ptr[2] = '1';
		} else {
			string_ptr[2] = '0';
		}

		if ((*number_ptr) & 0x10){
			string_ptr[3] = '1';
		} else {
			string_ptr[3] = '0';
		}

		if ((*number_ptr) & 0x08){
			string_ptr[4] = '1';
		} else {
			string_ptr[4] = '0';
		}

		if ((*number_ptr) & 0x04){
			string_ptr[5] = '1';
		} else {
			string_ptr[5] = '0';
		}

		if ((*number_ptr) & 0x02){
			string_ptr[6] = '1';
		} else {
			string_ptr[6] = '0';
		}

		if ((*number_ptr) & 0x01){
			string_ptr[7] = '1';
		} else {
			string_ptr[7] = '0';
		}

		string_ptr += 8;
		number_ptr -= 1;
	}

	(*string_ptr) = 0;

	return 0;
}

int Write_Dec_Nth_Number(
	struct Nth_Number * nth_number,
	char * string_buffer,
	size_t string_length
){
	return 0;
}

int Write_Hex_Nth_Number(
	struct Nth_Number * nth_number,
	char * string_buffer,
	size_t string_length
){
	char hex_table[] = "0123456789ABCDEF";
	size_t hex_index = 0;

	char * number_ptr = 0;
	char * number_end = 0;

	char * string_ptr = 0;

	if (!nth_number){
		return 1;
	}

	if (!nth_number->number_length){
		return 1;
	}

	if (!nth_number->number_buffer){
		return 1;
	}

	if (!string_length){
		return 1;
	}

	if (!string_buffer){
		return 1;
	}

	if (((nth_number->number_length * 2) + 1) > string_length){
		return 1;
	}

	number_end = nth_number->number_buffer;
	number_ptr = nth_number->number_buffer;
	number_ptr += nth_number->number_length;
	number_ptr -= 1;

	string_ptr = string_buffer;

	while ((number_ptr >= number_end)
	    && ((*number_ptr) == 0)){
		number_ptr--;
	}

	if (number_ptr < number_end){
		number_ptr = number_end;
	}

	while (number_ptr >= number_end){

		hex_index = 0;
		hex_index += (*number_ptr);
		hex_index &= 0xF0;
		hex_index >>= 4;

		(*string_ptr) = hex_table[hex_index];

		string_ptr++;

		hex_index = 0;
		hex_index += (*number_ptr);
		hex_index &= 0x0F;
		hex_index >>= 0;

		(*string_ptr) = hex_table[hex_index];

		string_ptr++;
		number_ptr--;
	}

	(*string_ptr) = 0;

	return 0;
}

int Write_Oct_Nth_Number(
	struct Nth_Number * nth_number,
	char * string_buffer,
	size_t string_length
){
	return 0;
}

int Write_Nth_File(
	char * file_name,
	void * write_buffer,
	size_t buffer_length
){
	char * buffer_ptr = 0;
	long int total_bytes_written = 0;
	long int bytes_written = 0;
	FILE * file = 0;

	if (!file_name){
		return NTH_ERROR_NULL_POINTER;
	}

	if (!buffer_length){
		return NTH_ERROR_ZERO_LENGTH;
	}


	if (!write_buffer){
		return NTH_ERROR_NULL_POINTER;
	}

	if (((long int) buffer_length) < 0){
		return NTH_ERROR_UNSPECIFIED;
	}

	buffer_ptr = write_buffer;

	file = fopen(file_name, "w");

	if (!file){
		return NTH_ERROR_UNSPECIFIED;
	}

	while (total_bytes_written < ((long int) buffer_length)){

		bytes_written = fwrite(
			&buffer_ptr[total_bytes_written],
			1,
			buffer_length - total_bytes_written,
			file
		);

		if (bytes_written < 0)
			break;

		total_bytes_written += bytes_written;
	}

	fclose(file);
	return 0;
}

int Write_Nth_Stack_Index(
	struct Nth_Program * nth_program
){
	int error = 0;

	char * stack_index_path = 0;
	struct Nth_Variable results_dir;

	FILE * stack_file = 0;
	size_t bytes_written = 0;

	if (!nth_program){
		return NTH_ERROR_NULL_POINTER;
	}

	results_dir.variable_name = "results-dir";
	results_dir.variable_value = 0;

	error = Get_Nth_Variable(
		nth_program,
		&results_dir
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Write_Nth_Stack_Index",
			"Failed to get results-dir variable"
		);

		return 1;
	}

	error = Create_Nth_Path(&stack_index_path);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Write_Nth_Stack_Index",
			"Failed to create stack index path"
		);

		return 1;
	}

	error = Set_Nth_Path_Prefix(
		&stack_index_path,
		results_dir.variable_value
	);

	if (error){

		Destroy_Nth_Path(&stack_index_path);

		Push_Nth_Failure(
			nth_program,
			"Write_Nth_Stack_Index",
			"Failed to set prefix of stack index path"
		);

		return 1;
	}

	error = Set_Nth_Path_Suffix(
		&stack_index_path,
		"stack-index.bin"
	);

	if (error){

		Destroy_Nth_Path(&stack_index_path);

		Push_Nth_Failure(
			nth_program,
			"Write_Nth_Stack_Index",
			"Failed to set suffix of stack index path"
		);

		return 1;
	}

	stack_file = fopen(
		stack_index_path,
		"w"
	);

	Destroy_Nth_Path(&stack_index_path);

	if (!stack_file){

		Push_Nth_Failure(
			nth_program,
			"Write_Nth_Stack_Index",
			"Stack file could not be opened"
		);

		return NTH_ERROR_NULL_POINTER;
	}

	bytes_written = fwrite(
		&nth_program->stack_index,
		sizeof(nth_program->stack_index),
		1,
		stack_file
	);

	if (bytes_written != 1){

		Push_Nth_Failure(
			nth_program,
			"Write_Nth_Stack_Index",
			"File writing was incomplete"
		);

		fclose(stack_file);

		return 1;
	}

	fclose(stack_file);

	return 0;
}

int Set_Nth_Number_Length(
	struct Nth_Number * nth_number,
	size_t new_length
){
	char * new_buffer = 0;

	size_t old_length = 0;

	if (!nth_number){
		return NTH_ERROR_NULL_POINTER;
	}

	new_buffer = realloc(
		nth_number->number_buffer,
		new_length
	);

	if (!new_buffer){
		return NTH_ERROR_NO_MEMORY;
	}

	old_length = nth_number->number_length;

	if (new_length > old_length){

		memset(
			&new_buffer[old_length],
			0,
			new_length - old_length
		);
	}

	nth_number->number_buffer = new_buffer;
	nth_number->number_length = new_length;

	return 0;
}

int Set_Nth_Number_Value(
	struct Nth_Number * nth_number,
	size_t value
){
	size_t value_size = 0;

	if (!nth_number){
		return NTH_ERROR_NULL_POINTER;
	}

	if (!nth_number->number_length){
		return NTH_ERROR_ZERO_LENGTH;
	}

	if (!nth_number->number_buffer){
		return NTH_ERROR_NULL_POINTER;
	}

	value_size = sizeof(value);

	if (value_size > nth_number->number_length)
		value_size = nth_number->number_length;

	memcpy(
		nth_number->number_buffer,
		&value,
		value_size
	);

	return 0;
}

